--[[ Copyright 2012, Chris Ho                               ]]--
--[[ MIT License (Please read the LICENSE file for details) ]]--

Use("common/Const");
Use("common/Log");
Use("common/Quicksort");
Use("common/List");
Use("common/Position");
Use("common/GamePiece");

-- A creature is any game piece with health (not an item).
-- id: creature ID
-- what: the object type (initially OBJECT_CREATURE)
--
-- maxhp: maximum health
-- maxsp: maximum spell points
Creature = {};

-- Generate a new creature template.
function Creature.Create(_id)
  local instance = GamePiece.Create(_id);

  instance.what = OBJECT_CREATURE;
  instance.maxhp = GetV(V_MAXHP, _id);
  instance.maxsp = GetV(V_MAXSP, _id);

  instance.Consider = Creature.Consider;
  instance.Assess = Creature.Assess;
  instance.Filter = Creature.Filter;
  instance.Scan = Creature.Scan;

  return instance;
end



--[[ Decide if the target is appropriate ]]--
-- Return true if the target is a threat (targeting us).
function Creature.Consider(self, _id)
  -- Don't consider ourselves.
  if (_id ~= self.id) then
    local intent = GetV(V_TARGET, _id);
    if (intent == self.id) then
      if (IsMonster(_id) == 1) then
        -- Object is a threat (hostile creature).
        return true;
      else
        -- Object is an object.
        local motion = GetV(V_MOTION, intent);
        if ((motion == MOTION_ATTACK) or (motion == MOTION_ATTACK2)) then
          -- The object is attacking us, which makes it a threat.
          return true;
        end
      end
    end
  end

  -- This target is not a threat.
  return false;
end

--[[ Look at a list of object IDs to measure the danger leavel ]]--
-- The danger level is the number of threats targeting this creature scaled
-- by the creature's health .
function Creature.Assess(self, _list)
  -- Get a list of all objects that target me.
  local danger = 0;
  local index, value;
  for index, value in ipairs(_list) do
    if (self:Consider(value) == true) then
      -- Each threat adds to our danger level.
      danger = danger + 1;
    end
  end

  -- Adjust danger to current health (if available).
  local hp = GetV(V_HP, self.id);
  if ((hp > 0) and (self.maxhp > 0)) then danger = danger * (self.maxhp / hp) end

  return danger;
end

--[[ Look for the closest objects ]]--
-- Pass in the actor list and the search range. You can
-- also pass in the maximum number of results. (Pass in
-- a limit of 0 to specify no limit.)
function Creature.Filter(self, _list, _range, _limit)
  local matches = {};
  local here = Position.Find(self.id);

  matches.n = 0;
  for index, value in ipairs(_list) do
    -- Skip ourselves in the search.
    if (value ~= self.id) then
      local there = Position.Find(value);
      if (Position.Measure(here, there) <= _range) then
        -- Remember the lists are one-indexed.
        matches.n = matches.n + 1;
        matches[matches.n] = GamePiece.Create(value);
      end
    end
  end

  -- Sorting Function (by threat, then distance, then by hp):
  local sorter = function (left, right)
    local from = Position.Find(self.id);

    if (GetV(V_TARGET, left.id) == self.id) then return -1
    elseif (GetV(V_TARGET, right.id) == self.id) then return 1 end

    local distance = Position.Measure(from, Position.Find(left.id)) - Position.Measure(from, Position.Find(right.id));
    if (distance ~= 0) then return distance end

    local health = GetV(V_HP, left.id) - GetV(V_HP, right.id);
    return health;
  end

  -- Now sort the list by distance.
  Quicksort.Sort(matches, 1, matches.n, sorter);

  -- Limit results to the total limit.
  if (_limit > 0) then
    -- If we have a limit, only return the closest matches.
    if (matches.n > _limit) then matches.n = _limit end
  end

  -- Extract just the IDs.
  local results = { n = matches.n };
  local index = 1;
  while (index <= matches.n) do
    results[index] = matches[index].id;
    index = index + 1;
  end

  return results;
end

--[[ Search the list of IDs for all objects that target this creature ]]--
-- We return a list of threat IDs. We find all threats in visible range.
function Creature.Scan(self, _list)
  -- Get a list of all objects that target me.
  local matches = {};
  local here = Position.Find(self.id);
  local index, value;

  matches.n = 0;
  for index, value in ipairs(_list) do
    local there = Position.Find(value);
    if (Position.Measure(here, there) < VISIBILITY) then
      if (self:Consider(value) == true) then
        -- Object is a threat.
        matches.n = matches.n + 1;
        matches[matches.n] = GamePiece.Create(value);
      end
    end
  end

  -- Sorting Function (by distance, then by hp):
  local sorter = function (left, right)
    local distance = Position.Measure(here, Position.Find(left.id)) - Position.Measure(here, Position.Find(right.id));
    if (distance ~= 0) then return distance end

    -- Note: This calculation may not be possible.
    local health = GetV(V_HP, left.id) - GetV(V_HP, right.id);
    return health;
  end

  -- Now sort the list by distance.
  Quicksort.Sort(matches, 1, matches.n, sorter);

  -- Extract just the IDs.
  local results = { n = matches.n };
  for index, value in ipairs(matches) do
    results[index] = value.id;
  end

  return results;
end
